#pragma once

#include "Pattern.h"
#include "IStimulus.h"

namespace SpikingNeuronLib
{
	// The input stimulus to a network
	// A firing pattern that is defined over one second
	// Most commonly created from a smaller pattern that is repeated at some frequency (repeats per second)
	// but the firing pattern can also be specified in full at construction time
	// See the Create methods for creating a Stimulus from a repeating pattern
	// Also see the Extension methods in the SpikingAnalyticsLib
	public ref class Stimulus : public IStimulus
	{
	protected:
		bool _isEmpty;										// true if there are no firing events in the stimulus
		int _patternStimulationsPerSecond;					// the number of repeats of an initialisation pattern (defaults to one)
		Pattern^ _pattern;									// the underlying pattern that is repeated one or more times per second
		array<array<int>^>^ _neuronFiringData;				// for each msec timeslot, the array of neuron indices that fire in that timeslot

		void Init(Pattern^ pattern, int patternStimulationsPerSecond, List<FiringEvent>^ oneSecondEventList);

	public:
		// Create a stimulus by repeating the pattern at the specified frequency
		Stimulus(int patternStimulationsPerSecond, String^ firingEvents);
		Stimulus(int patternStimulationsPerSecond, System::Collections::Generic::IEnumerable<FiringEvent>^ firingEvents);
		Stimulus(int patternStimulationsPerSecond, System::Collections::Generic::IEnumerable<Tuple<int, int>^>^ firingEvents);
		Stimulus(int patternStimulationsPerSecond, array<FiringEvent>^ firingArray);
		Stimulus(int patternStimulationsPerSecond, List<FiringEvent>^ firingsList);
		Stimulus(int patternStimulationsPerSecond, List<FiringEvent>^ firingsList, int uniqueID);

		/// Create a new stimulus by allocating the specified input period (in milliseconds) to each repetition of the pattern
		static Stimulus^ Create(Pattern^ pattern, int inputPeriod);

		/// Create a new stimulus by repeating the specified pattern the specified number of times each second
		static Stimulus^ Create(int patternStimulationsPerSecond, Pattern^ pattern);

		/// Create a new stimulus by allocating the specified input period (in milliseconds)
		/// to each repetition of the specified array of firing events
		static Stimulus^ Create(array<FiringEvent>^ firingEvents, int inputPeriod);

		/// Create a new stimulus by repeating the specified array of firing events the specified number of times each second
		static Stimulus^ Create(int patternStimulationsPerSecond, array<FiringEvent>^ firingEvents);

		/// Create a new stimulus by allocating the specified input period (in milliseconds)
		/// to each repetition of the specified list of firing events
		static Stimulus^ Create(List<FiringEvent>^ firingEvents, int inputPeriod);

		/// Create a new stimulus by repeating the specified list of firing events the specified number of times each second
		static Stimulus^ Create(int patternStimulationsPerSecond, List<FiringEvent>^ firingEvents);

		// IStimlus implementation -------------------------------------------------------------------------------------------

		property bool IsEmpty
		{
		public:
			virtual bool get()
			{
				// an input pattern was initialised but it has no elements
				return _isEmpty;
			}
		}

		virtual property HashSet<int>^ PatternNeurons
		{
		public:
			HashSet<int>^ get()
			{
				return _pattern->PatternNeurons;
			}
		}

		virtual array<array<int>^>^ GetNeuronFiringData(int second) { return _neuronFiringData; }
		virtual bool FiresAtTime(int second, int millisecond) { return _neuronFiringData[millisecond] != nullptr; }
		virtual array<int>^ GetFiringNeurons(int second, int millisecond) { return _neuronFiringData[millisecond]; }

		// -------------------------------------------------------------------------------------------

		// Create an empty stimulus
		static property SpikingNeuronLib::Stimulus^ Empty
		{
		public:
			SpikingNeuronLib::Stimulus^ get()
			{
				return gcnew Stimulus(0, gcnew array<FiringEvent>(0));
			}
		}

		/// The underlying pattern
		property SpikingNeuronLib::Pattern^ Pattern
		{
		public:
			SpikingNeuronLib::Pattern^ get()
			{
				return _pattern;
			}
		}

		/// A Pattern representing the firing events of the Stimulus over one second
		property SpikingNeuronLib::Pattern^ AsPattern
		{
		public:
			SpikingNeuronLib::Pattern^ get()
			{
				List<FiringEvent>^ oneSecondEventList = Stimulus::GetEventsFromRepeatedPattern(_pattern, _patternStimulationsPerSecond);
				return gcnew SpikingNeuronLib::Pattern(oneSecondEventList);
			}
		}

		/// The frequency in hertz
		property int PatternFrequency
		{
		public:
			int get()
			{
				return _patternStimulationsPerSecond;
			}
		}

		/// The number of milliseconds allocated to each pattern repetition
		property int InputPeriod
		{
		public:
			int get()
			{
				return 1000 / _patternStimulationsPerSecond;
			}
		}

		// Generate a new list of firing events by repeating the specified list of firing events
		static List<FiringEvent>^ GetEventsFromRepeatedPattern(List<FiringEvent>^ firingsList, int patternStimulationsPerSecond);

		// Generate a new list of firing events by repeating the specified sequence of firing events
		static List<FiringEvent>^ GetEventsFromRepeatedPattern(System::Collections::Generic::IEnumerable<FiringEvent>^ firingEvents, int patternStimulationsPerSecond);

		// Generate a new list of firing events by repeating the specified pattern
		static List<FiringEvent>^ GetEventsFromRepeatedPattern(SpikingNeuronLib::Pattern^ pattern, int patternStimulationsPerSecond);
	};
}
